<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* *{
      margin: 0;
      padding: 0;
    }
    ul, li{
      list-style: none;
    }
    div{
      display: flex;
      align-items: center;
      justify-content: center;
    }
    div{
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    } */

    /* nth-child 始终选中 demo 下的第二个元素 */
    /* .demo li:nth-child(2) {
      color: #ff0000;
    } */

    /* nth-of-type 选中指定标签的第二个元素，不指定元素，选中所有类型的第二个元素 */
    /* .demo li:nth-of-type(2) {
      color: #00ff00;
    }
    .test{
      display: flex;
      width: 100%;
      flex-wrap: wrap;
    }
    .test li{
      width: 25%;
      height: 100px;
      background: red;
      text-align: center;
      line-height: 100px;
    } */

    body {
      padding: 30px;
      text-align: center;
      /* 整体置灰 */
      /* filter: grayscale(100%); */
    }

    .container {
      position: relative;
      min-height: 200px;
    }

    .ball {
      position: absolute;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      border-radius: 50%;
      box-shadow: 0 0 5px rgba(0, 0, 0, 0.75);
      background: red;
    }

    .ball-running {
      animation: run-around 4s infinite;
    }

    @keyframes run-around {
      0% {
        /* top: 0;
        left:0; */
        transform: translate(0, 0);
      }

      25% {
        /* top: 0;
        left: 200px; */
        transform: translate(0, 200px);
      }

      50% {
        /* top: 200px;
        left: 200px; */
        transform: translate(200px, 200px);
      }

      75% {
        /* top: 200px;
        left: 0; */
        transform: translate(200px, 0);
      }
    }

    .box {
      display: flex;
      flex-direction: row-reverse;
    }

    .triangle {
      width: 0;
      height: 0;
      border-top: 40px solid red;
      /* border-right: 40px solid pink;
      border-bottom: 40px solid black;
      border-left: 40px solid blue; */
      border-right: 40px solid transparent;
      border-bottom: 40px solid transparent;
      border-left: 40px solid transparent;
    }

    /** 正三角 */
    .triangle1 {
      width: 0;
      height: 0;
      border-style: solid;
      border-width: 0 25px 40px 25px;
      border-color: transparent transparent rgb(245, 129, 127) transparent;
    }

    /** 倒三角 */
    .triangle2 {
      width: 0;
      height: 0;
      border-style: solid;
      border-width: 40px 25px 0 25px;
      border-color: rgb(245, 129, 127) transparent transparent transparent;
    }

    .scale-img {
      /* max-width: 160px; */
      transform: scale(0.5);
    }

    #box1 {
      display: flex;
    }

    .box1-item {
      flex: 1;
      background: red;
    }

    #box2 {
      display: flex;
      justify-content: space-between;
    }

    .box2-item {
      background: lightgreen;
    }

    #box3 {
      display: flex;
      background: yellowgreen;
    }

    .box3-item-1 {
      width: 100px;
      background: tomato;
    }

    .box3-item-2 {
      background: violet;
      flex-grow: 1;
      flex-shrink: 0;
      flex-basis: 200px;
    }

    .auto-img {
      /* vmin: 取vw和vh较小的值 */
      /* vmax: 取vw和vh较大的值 */
      width: 100vmin;
      height: 100vmin;
    }
  </style>
</head>

<body>
  <!-- <img class="auto-img" src="./src/image/4.jpg" alt="" srcset=""> -->
  <!-- <ul class="demo">
    <p>zero</p>
    <li>one</li>
    <li>two</li>
    <p>three</p>
  </ul>
  <ul class="test">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
  </ul> -->

  <!-- 如何实现让图片变成160px -->
  <!-- <img class="scale-img" src="https://www.baidu.com/img/flexible/logo/pc/result.png" style="width: 320px!important;" /> -->
  <div style="width: 140px;height: 90px;background: red;">
    <img src="https://resource.kaikeba.com/q_img/9171/4f56067b4a2b4260b0316f7b9e6aa7c4.png" alt="" srcset="">
  </div>
  <div style="width: 140px;height: 90px;background: blue;">
    <img src="https://resource.kaikeba.com/q_img/9171/53f7bd2eb817475087a1e898db15b08f.jpeg" alt="" srcset="">
  </div>
  <div style="width: 140px;height: 90px;background: yellow;">
    <img src="https://resource.kaikeba.com/q_img/9171/77cdf17b71cc4ed7a9a915fa1d8770ea.jpeg" alt="" srcset="">
  </div>
  <div class="box">
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
  </div>
  <div class="triangle"></div>
  <div class="triangle1"></div>
  <div>正三角 倒三角</div>
  <div class="triangle2"></div>
  <div class="container">
    <div class="ball" id="ball">121212</div>
  </div>

  <!-- ‌flex布局，里面五个元素，如果设置flex为1是什么情况？ -->
  <div id="box1">
    <div class="box1-item">1</div>
    <div class="box1-item">2</div>
    <div class="box1-item">3</div>
    <div class="box1-item">4</div>
    <div class="box1-item">5</div>
  </div>

  <!-- flex布局，怎么让两个元素，一个居左，一个居右 -->
  <div id="box2">
    <div class="box2-item">1</div>
    <div class="box2-item">2</div>
    <div class="box2-item">3</div>
  </div>

  <!-- flex布局 实现最小宽度 -->
  <div id="box3">
    <div class="box3-item-1">1</div>
    <div class="box3-item-2">2</div>
  </div>

  <ul id="container"></ul>

  <button id='button'>测试节流函数</button>

  <script src="./test.js"></script>
  <!-- <script src="./use-strict.js"></script> -->
  <!-- <script src="./src/Concurrent.Thread.js"></script> -->
  <script>
    function throttle(fn, wait) {
      let isFirst = true;
      let execDate = +new Date();
      let timerId = null;

      return function () {
        if (isFirst) {
          fn();
          execDate = +new Date();
          isFirst = false;
        } else {
          const currDate = +new Date();
          if (currDate - execDate >= wait) {
            fn();
            execDate = +new Date();
          } else {
            if (timerId) clearTimeout(timerId);

            const timeWait = execDate + wait - new Date();
            timerId = setTimeout(() => {
              fn();
              execDate = +new Date();
            }, timeWait);
          }
        }
      };
    }
    // 节流demo
    function log() {
      console.log(111);
    }
    const throttleLog = throttle(log, 2000)
    document.getElementById('button').addEventListener('click', throttleLog)
    // var balls = document.getElementById('ball')
    // balls.classList.add('ball')
    // balls.classList.add('ball-running')

    // 使用 <div> 画一个圆，大小位置都确定，然后用户点击屏幕；
    // 如果点击在圆内，输出 console.log(1)，
    // 如果点击在圆外，输出 console.log(2)
    // 使用一段JS实现这个场景
    document.body.addEventListener('click', e => {
      console.log(e.target.className);
      let className = e.target.className
      if (className === 'ball') {
        console.log('圆内');
      } else {
        console.log('圆外');
      }
    })

    // +function () {
    //   function a() { // 函数提升到顶部 函数提升优先级高于变量
    //     console.log(2);
    //   }
    //   var a // 变量提升到顶部    a没有赋值不会分配堆栈，直接忽视
    //   alert(a) // function a() {console.log(2)}
    //   a(); // 2
    //   a = function () {
    //     console.log(1);
    //   }
    //   // function a() { // 函数提升到顶部
    //   //   console.log(2);
    //   // }
    //   alert(a) // function a() {console.log(1)}
    //   a(); // 1

    //   // var c = d = a; 等价于下面
    //   d = a // d 没有用 var 声明所以是全局变量
    //   var c = b // c 是局部变量
    // }();
    // alert(d); // function a() {console.log(1)}
    // alert(c); // c is not defined

    // // 等价与上面执行顺序
    // +function () {
    //   console.log(a) // function a() {console.log(2)}
    //   a(); // 2
    //   var a = function () {
    //     console.log(1);
    //   }
    //   function a() {
    //     console.log(2);
    //   }
    //   console.log(a) // function a() {console.log(1)}
    //   a(); // 1
    //   var c = d = a;
    // }();
    // console.log(d); // function a() {console.log(1)}
    // console.log(c); // c is not defined


    // var a = { n: 1 };
    // var b = a;
    // a.x = a = { n: 2 };
    // // 正常连续赋值语句，可以拆成下面的，但是这里有一个 .运算符，优先级高于 =
    // // 所以先执行 a.x
    // // a = { n: 2 }
    // // a.x = a
    // console.log(a.x) // undefined   a.x先运行，是在原有指针{n:1}上取值，但是a指针移动，指向了{n:2}，断开了与{n:1}，所以是undefined
    // console.log(b.x) // {n: 2}   a指针移动指向{n:2} b也跟着指过来了, a.x指向{n:2}, 所以b.x也执行了{n:2} (a b按引用传递，指向的是同一块内存)


    // function test() {
    //   console.log(1)
    // }
    // function init() {
    //   if (false) {
    //     function test() {
    //       console.log(2)
    //     }
    //   }
    //   test() // 支持es6的称为现代浏览器，是块级作用域，test不存在提升；不支持的称为旧版本浏览器，函数会提升
    // };
    // init()


    // this.a = 20;
    // var test = {
    //   a: 40,
    //   init: () => {
    //     console.log(this.a); // 
    //     function go() {
    //       this.a = 60;
    //       console.log(this.a);
    //     }
    //     go.prototype.a = 50;
    //     return go;
    //   }
    // };
    // var p = test.init();
    // p();
    // // new (test.init())();

    // Object.prototype.a = 1; 
    // function Foo() {} 
    // var f = new Foo(); 
    // console.log(f.a);
    // // f.__proto__ = Foo.prototpe
    // // Foo.prototpe.__proto__ = Object.prototype
    // // Object.prototype.__proto = null



    let fun = (a, s) => {
      console.log(arguments)
    };
    // console.dir(fun); // --- > 从结果可以看到 箭头函数fun是没有prototype的。
    // fun(1,2) // arguments is not defined 从结果来看箭头函数没有arguments

    let func = function (a, s) {
      console.log(arguments)
    };
    // console.dir(func);
    // func(1,2) // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

    // .给一个字符串比如'abca'，返回第一个不重复的字母
    function search(str) {
      let obj = new Map()
      let idx = 0
      while (idx < str.length) {
        if (obj.has(str[idx])) {
          let value = obj.get(str[idx]) + 1
          obj.set(str[idx], value)
        } else {
          obj.set(str[idx], 1)
        }
        idx++
      }
      console.log(obj)
      for (const key of obj) {
        console.log(key)
        if (key[1] === 1) {
          return key[0]
        }
      }

      // let o = {}, arr = str.split('')
      // arr.forEach(element => {
      //   if (o[element]) {
      //     o[element] = o[element] + 1
      //   } else {
      //     o[element] = 1
      //   }
      // });

      // for (const key in o) {
      //   if (o[key] === 1) {
      //     return key
      //   }
      // }
    }
    // console.log(search('abca'))

    let MyComponent = function () {
      this.data = this.data()
    }

    MyComponent.prototype.data = function () {
      return {
        a: 1,
        b: 2
      }
    }

    let c1 = new MyComponent()
    let c2 = new MyComponent()
    c1.data.b = 5
    // console.log(c1, c2)

    // function sleep(time) {
    //   return new Promise((resolve, reject) => {
    //     setTimeout(resolve, time)
    //   })
    // }

    // async function test() {
    //   await sleep(1000)
    //   console.log(5)
    // }
    // test()

    // 实现一个sleep方法 sleep(5).catch() 5s后输出5
    // async function sleep(seconds){
    //   return await new Promise((resolve,reject)=>{
    //     setTimeout(()=>{
    //       reject(seconds);
    //     }, seconds * 1000)
    //   })
    // }

    // await sleep(5).catch((err) => console.log(err))

    function pro1() {
      return new Promise(function (resolve) {
        console.log("最外层-1");
        resolve();
      })
        .then(function () {
          console.log("最外层-2");
          pro2();
        })
        .then(function () {
          console.log("最外层-6");
        });
    }

    function pro2() {
      return new Promise(function (resolve) {
        console.log("pro2层-3");
        resolve();
      })
        .then(function () {
          console.log("pro2层-4");
          pro3();
        })
        .then(function () {
          console.log("pro2层-9");
        });
    }

    function pro3() {
      return new Promise(function (resolve) {
        console.log("pro3层-5");
        resolve();
      })
        .then(function () {
          console.log("pro3层-7");
          pro4();
        })
        .then(function () {
          console.log("pro3层-11");
        });
    }

    function pro4() {
      return new Promise(function (resolve) {
        console.log("pro4层-8");
        resolve();
      })
        .then(function () {
          console.log("pro4层-10");
        })
        .then(function () {
          console.log("pro4层-12");
        });
    }

    // pro1()

    // 第一个promise执行同步代码层 输出 最外层-1，
    // 进入外层第一个.then  输出 最外层-2
    //  
    // 并且运行pro2中同步代码 输出 pro2层-3， 也运行第一个.then  输出 pro2层-4
    // 因为在pro2中运行了pro3的异步，只会执行同步代码部份，所以还会在 输出 pro3层-5
    // 此时注意在往下就是嵌套层中的.then异步机制了，抛到异步队列中去了，
    // 因为每个异步.then执行都是同父级的promise同步的，例如父级执行到了第三个.then，那么子的promise元素也相应会被执行.then第三个。
    // 如上重复机制执行，下面就细说完所有执行过程
    //  
    // 上面完整输出是
    // 【最外层-1】
    // 【最外层-2】
    // 【pro2层-3】
    // 【pro2层-4】
    // 【pro3层-5】
    //  
    // 回第pro1执行第二个.then函数 输出 最外层-6
    // 此时回到pro2函数中，可以执行pro3第一个.then  输出 pro3层-7， 运行了pro4同步代码块 输出 pro4层-8
    // 并且可以执行第二个.then函数代码了 输出 pro2层-9，
    // 需要注意：pro3是在pro2.then中执行，所以在pro2.then第二个的时候，其实就是异步中执行了第一个.then，所以pro3此时才执行第一个.then。
    // 还有一个注意点：pro2.then此时在执行，后面的代码一定是在pro3先执行后才会触发，这里所说的执行后是指运行到指定层级.then停止，才回到pro2.then
    //  
    // 此部份完整输出
    // 【最外层-6】
    // 【pro3层-7】
    // 【pro4层-8】
    // 【pro2层-9】
    //  
    // 回到pro1执行第三个.then函数 此时没有，所以接着回到pro2内向下一直查找未执行完的.then
    // pro2二层.then都执行完了，查找pro3，因为pro2只有二层.then，并且在pro3中计算父层异步函数.then只算一层，所以pro3中只执行了一次.then。。。还有未被执行，所以此时默认上层执行了一次.then，
    //  
    // pro3先执行pro4第一个.then， 输出  pro4层-10
    // 在执行pro3中第二个.then函数  输出 pro3层-11
    //  
    // 此时pro3没有.then，包括根层的Pro1，不在细述（同上），所以直接执行pro4第二个.then函数代码块， 输出 pro4层-12
    // 此部份完整输出
    // 【pro4层-10】
    // 【pro3层-11】
    // 【pro4层-12】



    // 正则匹配片段二
    // 片段1、<h2 class="h4"><span class="label mr5">BOSS直聘</span></h2>
    // 片段2、<h2 class=\"h4\"><span class=\"label mr5\">BOSS直聘</span></h2>

    let str = '<h2 class=\"h4\"><span class=\"label mr5\">BOSS直聘</span></h2>'
    let reg = /<h2 class=\"h4\"><span class=\"label mr5\">BOSS直聘<\/span><\/h2>/
    // reg.test(str)


    var a = 10
    var foo = {
      a: 20,
      bar: function () {
        var a = 30;
        return this.a
      }
    }
    // this指向foo
    // console.log(foo.bar()) // 20 
    // 给表达式加了括号，括号的作用是改变表达式的运算顺序，在这里加不加括号无影响，相当于额foo.bar()
    // console.log((foo.bar)()) // 20 
    // 相当于重新给foo.bar赋值，即使 foo.bar = function () {var a = 30, return this.a}，此时this指向window
    // console.log((foo.bar=foo.bar)()) // 10 
    // 逗号运算符，逗号表达式求解过程是：先计算表达式1的值，在计算表达式2的值，一直到表达式n的值，最后整个逗号表达式的值就是表达式n的值
    // 逗号运算符的返回值是最后一个表达式的值。
    // 其实这里主要是经过逗号运算符计算后，就是纯粹的函数了，不是对象方法的引用了，所以这里的this指向window
    // console.log((foo.bar, foo.bar)()) // 10 
    // 第三问，第四问，一个是等号运算符，一个是逗号运算符，可以这么理解经过赋值，运算符运算后，都是纯粹的函数了，不是对象方法的引用了，所以this指向的都是window


    // 3.每隔一秒钟输出一个数字，要求顺序如下：
    // > 1
    // > 间隔一秒钟
    // > 2
    // > 间隔一秒钟
    // > 3
    // > 间隔一秒钟
    // > 4
    // > 间隔一秒钟
    // > 5

    // 法一 自执行函数
    // for(var i = 1; i <= 5; i++) {
    //   (function (i) { 
    //     setTimeout(() => {
    //       console.log(i)
    //       console.log('间隔一秒钟')
    //     }, 1000 * i)
    //   })(i)
    // }

    // 法二 es6块级作用域
    // for(let i = 1; i <= 5; i++) {
    //   (function () {
    //     setTimeout(() => {
    //       console.log(i)
    //       console.log('间隔一秒钟')
    //     }, 1000 * i)
    //   })()
    // }

    // 法三 setInterval
    // let idx = 0
    // let timerId = setInterval(() => {
    //   idx++
    //   console.log(idx)
    //   console.log('间隔一秒钟')
    //   if(idx >= 5) clearInterval(timerId)
    // }, 1000)

    // 法四 传址传递
    // let output = (i) => {
    //   setTimeout(() => {
    //     console.log(i)
    //     console.log('间隔一秒钟')
    //   }, 1000 * i)
    // }
    // for(var i = 1; i <= 5; i++) {
    //   output(i)
    // }

    // 法五 promise.all
    // let promises = []
    // let output = function (i) {
    //   return new Promise(resolve => {
    //     setTimeout(() => {
    //       console.log(i)
    //       console.log('间隔一秒钟')
    //     }, 1000 * i)
    //     resolve()
    //   })
    // }
    // for (let index = 1; index <= 5; index++) {
    //   promises.push(output(index))
    // }
    // Promise.all(promises).then(res => console.log(res))

    // 法六 async await
    // function sleep(time) {
    //   return new Promise((resolve, reject) => {
    //     setTimeout(resolve, time)
    //   })
    // }
    // (async function () {
    //   for (let index = 1; index < 6; index++) {
    //     await sleep(1000)
    //     console.log(index)
    //     console.log('间隔一秒钟')
    //   }
    // })()

    let list = [
      {
        "title": "其它",
        "value": "channel-其它",
        "key": "channel-其它",
        "children": [{
          "title": "马东",
          "value": "albb",
          "key": "albb",
          "children": [
            {
              "title": "李三",
              "value": "lisan",
              "key": "lisan"
            },
            {
              "title": "王五",
              "value": "wangwu",
              "key": "wangwu"
            },
          ]
        }, {
          "title": "王楠",
          "value": "jingdong",
          "key": "jingdong"
        }]
      },
      {
        "title": "保分期内测渠道",
        "value": "channel-保分期内测渠道",
        "key": "channel-保分期内测渠道",
        "children": [{
          "title": "梁兴",
          "value": "baofenqitest",
          "key": "baofenqitest"
        }, {
          "title": "yanshou",
          "value": "aibaozhang",
          "key": "aibaozhang"
        }, {
          "title": "员工一",
          "value": "yuangong1",
          "key": "yuangong1"
        }]
      }
    ]

    // console.log(list)

    function flatten(arr = []) {
      return arr.reduce((lastRes, item, index) => {
        if (!Array.isArray(item.children)) {
          return lastRes.concat([item])
        }
        return lastRes.concat(flatten(item.children))
      }, [])
    }

    // function flatten(arr = []) {
    //   return arr.map(o => o.children ? [...flatten(o.children)] : [o]).flat()
    // }

    // console.log(flatten(list))

    // 队列实现
    var obj3 = {
      title: "标题一",
      Children: [
        { title: "标题二" },
        {
          title: "标题三",
          Children: [
            { title: "标题4" }
          ]
        }
      ]
    }

    function c(obj3) {
      let data = []
      let root = [obj3]
      while (root.length) {
        let length = root.length
        let i = 0
        while (i++ < length) {
          // 出列 取出队列中的第一个
          let top = root.shift()
          // 添加值
          data.push({ title: top.title })
          // 入列
          if (top.Children && top.Children.length > 0) {
            for (let i = 0; i < top.Children.length; i++) {
              root.push(top.Children[i])
            }
          }
        }
      }
      return data
    }
    // console.log(c(obj3))

    {
      let obj = {
        '2': 3,
        '3': 4,
        'length': 2,
        'splice': Array.prototype.splice,
        'push': Array.prototype.push
      }
      // 对象挂载了push。length就是一个伪数组方法，能支持相应方法
      // 但里面的值是固定的，起始插入的值，从你的第0个开始
      // 默认长度从2开始计算
      // 你只要push了，长度就加1
      obj.push(1)
      obj.push(2)
      // 最终是这样的伪数组
      // {2: 1, 3: 2, length: 4, push: ƒ}
      // 2: 1
      // 3: 2
      // length: 4
      // push: ƒ push()

      // console.log(obj)
    }

    {
      function changeObjProperty(o) {
        // 函数的形参是按值传递的
        o.siteUrl = "http://www.baidu.com"
        o = new Object() // 形参 o 的指向发生改变，指向堆内存中一个新的对象，局部作用域外部访问不到
        o.siteUrl = "http://www.google.com"
        // console.log(o.siteUrl)
      }
      let webSite = new Object();
      changeObjProperty(webSite);
      // 访问的是全局，内部变量访问不到
      // console.log(webSite.siteUrl); // http://www.baidu.com
    }

    {
      function Foo() {
        Foo.a = function () {
          console.log(1)
        }
        this.a = function () {
          console.log(2)
        }
      }
      // 以上是Foo的构建方法，没有产生实例，此刻也没有执行

      Foo.prototype.a = function () {
        console.log(3)
      }
      // Foo的原型上挂在a方法 输出3

      Foo.a = function () {
        console.log(4)
      }
      // Foo上直接挂在了a方法 输出4

      // 立刻执行Foo上得方法 输出4
      // Foo.a(); // 4

      /* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事：
      1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。
      2. 在新对象上挂载直接方法 a ，输出值为 2。
      */
      // let obj = new Foo();

      // 因为有直接方法 a ，不需要去访问原型链，所以使用的是构建方法里所定义的 this.a，
      // # 输出 2
      // obj.a(); // 2

      // 构建方法里已经替换了全局 Foo 上的 a 方法，所以
      // # 输出 1
      // Foo.a(); // 1

    }

    {
      // var name = 'Tom';
      // (function() {
      //   if (typeof name == 'undefined') {
      //     var name = 'Jack';
      //     console.log('Goodbye ' + name);
      //   } else {
      //     console.log('Hello ' + name);
      //   }
      // })();

      // var name = 'Tom';
      // (function() {
      //   if (typeof name == 'undefined') {
      //     name = 'Jack';
      //     console.log('Goodbye ' + name);
      //   } else {
      //     console.log('Hello ' + name);
      //   }
      // })();
    }

    {
      // 1 + "1" // '11'
      // 2 * "2" // 4
      // [1, 2] + [2, 1] // '1,22,1' 数组相加会先调用toString方法
      // "a" + + "b" // 'aNaN'
    }

    // 为什么 for 循环嵌套顺序会影响性能？
    {
      // 相同循环次数，外层越大，越影响性能
      // let t1 = new Date().getTime()
      // for (let i = 0; i < 100; i++) {
      //   for (let j = 0; j < 1000; j++) {
      //     for (let k = 0; k < 10000; k++) {
      //     }
      //   }
      // }
      // let t2 = new Date().getTime()
      // console.log('first time', t2 - t1)

      // for (let i = 0; i < 10000; i++) {
      //   for (let j = 0; j < 1000; j++) {
      //     for (let k = 0; k < 100; k++) {

      //     }
      //   }
      // }
      // let t3 = new Date().getTime()
      // console.log('two time', t3 - t2)
    }

    {
      function wait() {
        return new Promise(resolve =>
          setTimeout(resolve, 1 * 1000)
        )
      }

      async function main() {
        console.time();
        const x = wait();
        const y = wait();
        const z = wait();
        // x y z 同步执行 所以一共等待1s
        await x;
        await y;
        await z;

        // 异步执行，上一个执行完毕，才会执行下一个 所以一共等待3s
        // await wait()
        // await wait()
        // await wait()
        console.timeEnd();
      }
      // main();
    }

    {
      // 找出字符串中连续出现最多的字符和个数
      // 'abcaakjbb' => {'a':2,'b':2}
      // 'abbkejsbcccwqaa' => {'c':3}

      function findLongest(str) {
        let map = new Map()
        for (let index = 0; index < str.length; index++) {
          const el = str[index]
          if (map.has(el)) {
            map.set(el, map.get(el) + 1)
          } else {
            map.set(el, 1)
          }
        }
        let max = 0, res = {}
        for (const [key, value] of map) {
          if (value >= max) {
            max = value
            res[key] = value
          }
        }
        return res
      }

      // console.log(findLongest('abbkejsbcccwqaa'))
    }

    {
      // 如何让 a==1&&a==2&&a==3 得值为true？
      // a===1&&a===2&&a===3 呢？

      // == 的话可以重写对象得 valueOf 或 toString 方法
      // let a = {
      //   value: 1,
      //   valueOf: () => {
      //     return a.value ++
      //   }
      // };
      // let a = {
      //   value: 1,
      //   toString: () => {
      //     return a.value ++
      //   }
      // };

      // == 的话还可以利用数组得api shift赋值给join实现
      // let a = [1,2,3]
      // a.join = a.shift

      // === 的话可以用 Object.defineProperty 
      // 注意块级作用域下没有window 测试时注意
      // 劫持window上面的a，当a每一次判断时会触发get属性
      // let value = 1
      // Object.defineProperty(window, 'a', {
      //   get() {
      //     return value++
      //   }
      // })

      if (a == 1 && a == 2 && a == 3) {
        console.log(1);
      }
    }

    {
      let person = {
        name: '小月'
      }
      const memary = person
      // 手动清空，GC不会立即回收，下次GC才会回收
      person = null
      // console.log(memary)

      // WeakMap 可以立即回收
      let p = new WeakMap()
      let key = {
        name: '小月'
      }
      p.set(key, 'hello')
      const m = p
      p.delete(key)
      // console.log(p.get(key))
      // console.log(m.get(key))
    }

    {
      // 不借助中间变量，交换a b的值
      var a = 1
      var b = 2
      // 利用加法，有溢出风险
      // b = a + b
      // a = b - a
      // b = b - a

      // 利用减法可以避免溢出风险
      b = a - b
      a = a - b
      b = a + b
      // console.log(a, b)
    }

    {
      // 找出数组中任意数相加为目标值得组合
      // [1, 2, 6, 10, 5, 4, 8, 3] => 10
      let arr = [1, 2, 6, 10, 5, 4, 8, 3]
      let arrs = arr.sort((a, b) => Math.random() - 0.5)
      // console.log(arr)
      // console.log(arrs)
    }

    {
      // 实现multiply方法，返回如下格式的值
      // Array.prototype.multiply = function () {}; // 实现
      // var a = [1, 2, 3, 4, 5];
      // a.multiply();
      // console.log(a); // 输出如下 [1, 2, 3, 4, 5, 1, 4, 9, 16, 25]

      Array.prototype.multiply = function () {
        let arr1 = this
        let arr2 = [...arr1]
        // return [...arr1, ...arr2.map(el => el * el)]
        return arr1.concat(arr2.map(el => el * el))
      }
      // console.log([1, 2, 3, 4, 5].multiply())
    }

    {
      function fn1() {
        for (var i = 0; i < 4; i++) {
          var timer = setTimeout(function (i) {
            console.log(i);
            clearTimeout(timer)
          }, 10, i);
        }
      }
      // fn1();

      function fn2() {
        for (var i = 0; i < 4; i++) {
          var timer = setInterval(function (i, timer) {
            console.log(i);
            clearInterval(timer)
          }, 10, i, timer);
        }
      }
      // fn2();
    }

    {
      /**
       * 请处理给定字符串:
       *   - 去掉无用字符和乱码, 只保留大小写英文字母, 单引号, 和空格
       *   - 把一个或多个连续无用字符和乱码换成一个空格
       * @param str: 字符串, 例 "I'm我driving是to乱Beijing码after breakfast"
       * @return str: 例 "I'm driving to Beijing after breakfast"
       */
      function handleStr(str) {
        let newStr = '';
        let reg = /^[A-Za-z]|[\s]|[\']+$/; // 正则匹配大小写英文字母, 单引号, 和空格
        for (let i = 0; i < str.length; i++) {
          if (reg.test(str[i])) {
            newStr += str[i]
          } else {
            // 判断是否是连续乱码，如果是值替换一个空格即可
            if (reg.test(str[i + 1])) {
              newStr += ' '
            } else {
              newStr += ''
            }
          }
        }

        // 加入乱码只限中文
        // return str.replace(/[\u4e00-\u9fa5]+/g, ' ');
        return newStr
      }
      // console.log(handleStr("I'm我driving是to乱Beijing码码after breakfast"));
    }

    {
      // 删除字符串中出现次数最少得字符 如 asdfasdfasdfzx => asdfasdfasdf
      function handleStr(str) {
        // let obj = {}
        // for(let i = 0; i < str.length; i++) {
        //   if (obj[str[i]]) {
        //     obj[str[i]] = obj[str[i]] + 1
        //   } else {
        //     obj[str[i]] = 1
        //   }
        // }

        let obj = str.split('').reduce((lastRes, item) => {
          if (lastRes[item]) {
            lastRes[item] = lastRes[item] + 1
          } else {
            lastRes[item] = 1
          }
          return lastRes
        }, {})

        let values = Object.values(obj).sort((a, b) => a - b)
        let min = Object.keys(obj).filter(item => obj[item] === values[0])

        return str.split('').filter(item => !min.includes(item)).join('')
      }
    }

    {
      // 转换字符串如下
      // 20200819155130 =》2020年08月19日 15:51:30
      // console.time('trans')
      // function transformStr(str) {
      //   let arr = []
      //   let obj = {
      //     '0': '年',
      //     '1': '月',
      //     '2': '日',
      //     '3': ' ',
      //     '4': ':',
      //     '5': ':'
      //   }

      //   for (let i = 0; i < str.length; i += 2) {
      //     if (i === 0) {
      //       arr.push(str.slice(i, i + 4))
      //     } else {
      //       arr.push(str.slice(i, i + 2))
      //     }
      //   }

      //   return arr.reduce((lastRes, item, index) => {
      //     lastRes += item
      //     obj[index] ? lastRes += obj[index] : lastRes
      //     return lastRes
      //   }, '')
      // }
      // console.timeEnd('trans')

      // console.time('trans')
      function transformStr(str) {
        // let match = /(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})/g.exec('20180806');
        // let regArr = str.match(/\d{2}/g);
        let match = /(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})(?<hour>\d{2})(?<minute>\d{2})(?<second>\d{2})/g.exec(str);

        let timeMap = {
          'year': '年',
          'month': '月',
          'day': '日 ',
          'hour': ':',
          'minute': ':',
          'second': ''
        }
        return Object.entries(match.groups).reduce((lastRes, [key, value]) => {
          lastRes += (value + timeMap[key])
          return lastRes
        }, '')
      }
      // console.timeEnd('trans')

      // console.log(transformStr('20200819155130'));
    }

    {
      // [url1, url2, ...] request or fetch请求 要求快速请求 快速返回，并且保证顺序
      function fetchData(urls = []) {
        return new Promise((resolve, reject) => {
          let result = []
          let count = 0
          urls.forEach((url, idx) => {
            fetch(url).then(res => {
              count++
              result[idx] = res
              if (count === urls.length) {
                resolve(result)
              }
            })
          })
        })
      }
    }

    {
      // 封装一个网络请求功能，已经获取的数据缓存下来，下次再发送相同url请求时将从缓存中获取，
      // ajax的请求可以直接使用ajax(url, callback)的形式

      function getData(url) {
        let urlsMap = {}
        let flag = true
        let status = 'done'
        let cbs = []
        return new Promise((resolve, reject) => {
          if (!flag) return

          if (urlsMap[url]) {
            resolve(urlsMap[url])
          } else {
            ajax(url, res => {
              urlsMap[url] = res
              flag = false
              resolve(res)
            })
          }
        }).finally(() => {
          flag = true
        })
      }
    }

    // {
    //   // setTimeout 延时准确么？
    //   var date = new Date();
    //   setTimeout(function (e) {
    //     var currentDate = new Date();
    //     console.log(currentDate - date);
    //   }, 100);


    //   // 给定一个数组，如何通过Math.max获取最大
    //   let arr = [1, 2, 3, 4, 5]
    //   console.log(Math.max(...arr))
    //   console.log(Math.max.apply(null, arr))
    // }

    {
      // Number('1a') // NaN
      // parseInt('1a')
      // 3.toString() // 报错
      // 3.2.toString()

      // a b c 三个方法，要求c有方法a b的方法和属性
      // 我想到了继承 c继承b继承a

      // function A(name) {
      //   this.name = name
      //   this.sayA = function () {
      //     console.log('A')
      //   }
      // }
      // // A.prototype.sayA = function() {
      // //   console.log('A')
      // // }

      // function B(age, name) {
      //   A.call(this, name)
      //   this.age = age
      //   this.sayB = function () {
      //     console.log('B')
      //   }
      // }
      // // B.prototype.sayB = function() {
      // //   console.log('B')
      // // }

      // function C(sex) {
      //   B.call(this)
      //   this.sex = sex
      //   this.sayC = function () {
      //     console.log('C');
      //   }
      // }
      // // C.prototype.sayC = function () {
      // //   console.log('C');
      // // }

      // // 原型链继承
      // // C.prototype = new B()
      // // B.prototype = new A()

      // let __proBto__b = Object.create(B.prototype) // 创建B的原型链副本
      // __proBto__b.constructor = C // 修改 __proto__b 的this指向C
      // C.prototype = __proBto__b // 把B上的方法拷贝到C上

      // let __proBto__a = Object.create(A.prototype) // 创建A的原型链副本
      // __proBto__a.constructor = B // 修改__proBto__a的this指向B
      // B.prototype = __proBto__a // 把A上的方法拷贝到B上

      // let c = new C()
      // c.sayC()
      // c.sayB()
      // c.sayA()


      // 找出数组中出现次数最多的元素（注意有相同的）
      const arr = ['1', 1, '1', 2, 2, 2, 1, 4, 5, 6, '1']
      function getMaxStr(arr) {
        let obj = {};
        let maxs = [], chars = [] // 最大字符串可能重复，所以用数组表示
        for (let i = 0; i < arr.length; i++) {
          let el = arr[i]
          if (typeof el === 'string') { // 对字符串1和数字1作区分
            el = el + '*'
          }

          if (obj[el]) {
            obj[el]++
          } else {
            obj[el] = 1
          }
          // console.log(obj[el]);
          // if (obj[el] >= max) {
          //   max = el
          //   maxs.push(obj[el])
          //   chars.push(el)
          // }
        }
        console.log(Object.values(obj));
        let max = Math.max(...Object.values(obj))
        console.log('max: ', max);
        for (const key in obj) {
          if (obj.hasOwnProperty(key)) {
            if (obj[key] === max) {
              chars.push(key.indexOf('*') ? key.replace('*', '') : key)
            }
          }
        }
        console.log(max);
        console.log(maxs, chars);
      }
      // getMaxStr(arr)

      function a() {
        console.log('a');
      }
      function sleep(ts) {
        console.log(`等待${ts / 1000}s`);
        return new Promise((resolve, reject) => {
          setTimeout(resolve, ts)
        })
      }
      function b() {
        console.log('b');
      }
      // (async() => {
      //   await a()
      //   await sleep(3000)
      //   await b()
      // })()
    }

    {
      // 利用promise实现async

      // async function p1() {
      //   console.log(1);
      //   return 1
      // }
      // // 等效
      // function p1() {
      //   console.log(1);
      //   return Promise.resolve(1)
      // }

      // async function p2() {
      //   console.log(2);
      //   let a = await new Promise(resolve => {
      //     setTimeout(() => {
      //       resolve(1)
      //     }, 1000)
      //   })
      //   console.log(a);
      //   return 2
      // }
      // // 等效
      // function p2() {
      //   console.log(2);
      //   return Promise.resolve(new Promise((resolve, reject) => {
      //     setTimeout(() => {
      //       resolve(1)
      //     }, 1000)
      //   }).then(res => {
      //     let a = res
      //     console.log(a);
      //     return Promise.resolve(2)
      //   }))
      // }
      // p2()

      // let p1 = () => new Promise(resolve => setTimeout(() => resolve(1), 1000))
      // let p2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000))
      // let p3 = () => new Promise(resolve => setTimeout(() => resolve(3), 3000))
      // let ps = [p1, p2, p3]
      // async function p() {
      //   for (let i = 0; i < ps.length; i++) {
      //     let res = await ps[i]()
      //     console.log(res);          
      //   }
      // }
      // // 等效
      // async function p() {
      //   let queue = []
      //   for (let i = 0; i < ps.length; i++) {
      //     queue.push(() => ps[i]().then(res => console.log(res)))
      //   }
      //   queue.reduce((lastRes, item) => lastRes.then(res => item()), Promise.resolve())
      // }
      // p()
    }

    {
      // 深拷贝 考虑所有
      // 手写koa的next方法
      // EventEmit 方法 on off emit once
    }

    {
      // 链式调用
      // function Student() {}
      // Student.prototype.setAge = function (age) {
      //   this.age = age;
      //   return this;
      // };
      // Student.prototype.setName = function (name) {
      //   this.name = name;
      //   return this;
      // };
      // Student.prototype.say = function () {
      //   return `年龄 ${this.age}，姓名 ${this.name}`;
      // };

      // const student = new Student();
      // const res = student.setAge(18).setName('小月').say();
      // console.log('res: ', res);

      // const student = {
      //   age: 0,
      //   name: '',
      //   setAge: function (age) {
      //     this.age = age;
      //     return this;
      //   },
      //   setName: function (name) {
      //     this.name = name;
      //     return this;
      //   },
      //   say: function () {
      //     return `年龄 ${this.age}，姓名 ${this.name}`;
      //   },
      // };
      // const res1 = student.setAge(18).setName('小月').say();
      // console.log('res: ', res1);
    }

    {
      // 判断数组的四种方法
      // console.log(Array.isArray([])); // true
      // console.log([] instanceof Array); // true
      // console.log([].contructor === Array); // true
      // console.log(Object.prototype.toString.call([])); // '[object Array]'
    }

    {
      // 通过 performance.timing 壣廳攇，可以获取各个阶段的执行时间：
      // {
      //   navigationStart: 1578537857229; //上一个文档卸载(unload)结束时的时间戳
      //   unloadEventStart: 1578537857497; //表征了unload事件抛出时的时间戳
      //   unloadEventEnd: 1578537857497; //表征了unload事件处理完成时的时间戳
      //   redirectStart: 0; // 重定向开始时的时间戳
      //   redirectEnd: 0; //重定向完成时的时间戳
      //   fetchStart: 1578537857232; //准备好HTTP请求来获取(fetch)文档的时间戳
      //   domainLookupStart: 1578537857232; //域名查询开始的时间戳
      //   domainLookupEnd: 1578537857232; //域名查询结束的时间戳
      //   connectStart: 1578537857232; //HTTP请求开始向服务器发送时的时间戳
      //   connectEnd: 1578537857232; //浏览器与服务器之间的连接建立时的时间戳
      //   secureConnectionStart: 0; //安全链接的握手时的U时间戳
      //   requestStart: 1578537857253; //HTTP请求（或从本地缓存读取）时的时间戳
      //   responseStart: 1578537857491; //服务器收到（或从本地缓存读取）第一个字节时的时间戳。
      //   responseEnd: 1578537857493; //响应结束
      //   domLoading: 1578537857504; //DOM结构开始解析时的时间戳
      //   domInteractive: 1578537858118; //DOM结构结束解析、开始加载内嵌资源时的时间戳
      //   domContentLoadedEventStart: 1578537858118; //DOMContentLoaded 事件开始时间戳
      //   domContentLoadedEventEnd: 1578537858118; //当所有需要立即执行的脚本已经被执行（不论执行顺序）时的时间戳
      //   domComplete: 1578537858492; //当前文档解析完成的时间戳
      //   loadEventStart: 1578537858492; //load事件被发送时的时间戳
      //   loadEventEnd: 1578537858494; //当load事件结束时的时间戳
      // }
    }

    {
      // 如何执行下面的字符串函数？
      const code = `function getUser() {console.log(111);}`;
      // var fn = new Function(`return ${code}`)();
      // fn();
      // eval(`(${code})`)();
    }

    {
      // 原型链的关系
      function Foo() { }
      let f = new Foo();

      // 对象原型
      f.__proto__ === Foo.prototype;
      Foo.prototype.__proto__ === Object.prototype;
      Object.prototype.__proto__ === null;

      // 构造函数的原型属性的constructor指向构造函数自己
      Foo.prototype.constructor === Foo;
      Function.prototype.constructor === Function;
      Object.prototype.constructor === Object;

      // 函数原型
      Foo.__proto__ === Function.prototype; // Object.__proto__ === Function.prototype
      Function.__proto__.__proto__ === Object.prototype;
      Object.prototype.__proto__ === null;

      // 大函数Function 特殊
      Function.__proto__ === Function.prototype;

      // 所有对象都有__proto__  函数才有prototype

      f.constructor // function Foo() { }
      Foo.prototype = {};
      let f1 = new Foo();
      f1.constructor // Object() { [native code] }
      // 1 f f1中是没有constructor的；
      // 2 但是会从构造函数的prototype中查找，相当于Foo.prototype.constructor；
      // (f.constructor === f.__proto__.constructor === Foo.prototype.constructor === Foo)
      // f2被从新定义了，指向了object
      // (f1.constructor == f1.__proto__.constructor === Object.prototype.constructor === Object)

      // 实例的constructor(实例中没有constructor，会从原型上查找)
      f.constructor === Foo.prototype.constructor;
      f.__proto__.constructor === Foo.prototype.constructor;
      Foo.prototype.constructor === Foo;
    }

    {
      // fetch请求结果总结：
      // 1 const res = await fetch('/').then(); 返回整个Response相应体
      // 2 const res = await fetch('/'); 返回整个Response相应体
      // 3 const res = await fetch('/').then(res => 123); 返回123
      // 4 const res = await fetch('/').then(res => res.json()); 返回正常的带有code码的数据结构
    }

    {
      // 执行上下文栈(Execution context stack简称ECS)
      // function first() {
      //   second();
      //   console.log(1);
      // }
      // function second() {
      //   third();
      //   console.log(2);
      // }
      // function third() {
      //   console.log(3);
      // }
      // first(); // 3 2 1
      // {
      //   // 执行过程大致如下
      //   // 1.代码执行前创建全局执行上下文推入执行栈
      //   ECStack = [globalContext];
      //   // 2.first调用 创建first函数执行上下文推入栈
      //   ECStack.push(firstContext);
      //   // 3.first调用了 second ,创建second函数执行上下文推入栈
      //   // 等待second 执行完毕再输出1
      //   ECStack.push(secondContext);
      //   // 4.second 调用了third,创建third函数执行上下文推入栈
      //   // 等待third执行完再输出2
      //   ECStack.push(thirdContext);
      //   // 5.third执行完毕，输出3，弹出栈
      //   ECStack.pop(thirdContext);
      //   // 6.second执行完毕，输出2，弹出栈
      //   ECStack.pop(secondContext);
      //   // 7.first执行完毕，输出1，弹出栈
      //   ECStack.pop();
      //   // 此时执行栈中只剩下一个全局执行上下文
      // }
    }

    {
      // promise通过catch捕获到reject之后，在catch后面还可以继续顺序执行then方法，但是只执行then的第一个回调(resolve回调)
      // Promise.reject(2)
      //   .catch(r => {
      //     // 捕获到错误，执行
      //     console.log('catch1');
      //   })
      //   // 错误已经被捕获，后边的`then`都顺序执行，且只执行`then`的第一个回调（resolve的回调）
      //   .then(v => {
      //     console.log('then1');
      //   }, r => {
      //     console.log('catch2');
      //   })
      //   .catch(r => {
      //     // 前边没有未捕获的错误，不执行
      //     console.log('catch3');
      //   })
      //   .then(v => {
      //     console.log('then2');
      //   }, r => {
      //     console.log('catch4');
      //   });
      // 结果会打印：catch1、then1、then2
    }

    {
      const o = {};

      o['b'] = 'b';
      o['a'] = 'a';
      o['c'] = 'c';

      o[3] = 3;
      o[2] = 2;
      o[1] = 1;
      // console.log(o);
      // 对象内部分为两个执行栈处理，一个处理数字相关的，另一个处理非数字相关的；
      // 处理数字的会按照数字升序排序；
      // 处理非数字的是按照时间插入顺序，即最晚插入的在最后面；
      // 另外Map数据机构是按照时间插入顺序，不会区分数字；
    }

    {
      // 如何统计当前页面发出多少个请求，比如当前有 fetch, xhr
      // 1 代理一下，搞个统一入口
      // 2 重写xhr得send方法 重写fecth
      // 3 window.performance.getEntries(有局限性 只统计一次你当前页面的这个get请求，页面加载之后，其他一切请求，它是不负责的)，所以最好的处理办法还是重写请求方法做处理
      (function () {
        // 浏览器不支持处理
        if (!window.performance && !window.performance.getEntries) {
          return false;
        }

        var result = [];
        // 获取当前页面所有请求对应的PerformanceResourceTiming对象进行分析
        window.performance.getEntries().forEach((item) => {
          result.push({
            'url': item.name,
            'entryType': item.entryType,
            'type': item.initiatorType, // 每个请求类型
            'duration(ms)': item.duration
          });
        });
        // fetch, xhr请求结果，可以根据请求有类型对result过滤所得
        // console.table(result);
      })();
    }

    {
      // 页面中的dom如何确保只能被添加一个addEventListener？
      // 思路：重写addEventListener方法利用切面思想解决
      const addEventListener = EventTarget.prototype.addEventListener;
      EventTarget.prototype.addEventListener = function (event, ...args) {
        if (this['_' + event]) {
          return false;
        } else {
          this['_' + event] = true;
          addEventListener.call(this, event, ...args);
        }
      };
      // 测试
      // const div = document.createElement('div');
      // div.addEventListener('click', () => {
      //   console.log(1);
      // });
      // div.addEventListener('click', () => {
      //   console.log(2);
      // });
      // div.click();
    }

    {
      // 如何防止重复请求？
      // 思路：将请求的实例先存起来，成功或失败都可以感知，然后将实例变量置空，便于下次请求 
      let count = 1;
      let promiseFn = () => {
        return new Promise((rs) =>
          window.setTimeout(() => {
            rs(count++);
          })
        );
      };
      function firstPromise(promiseFn) {
        let p = null;
        return (...args) => {
          // 请求的实例已存在，意味着正在请求，直接返回该实例，不触发新的请求
          return p
            ? p
            : // 请求实例不存在，发送新的请求，且在finally将p置为空
            (p = promiseFn.apply(this, args).finally(() => (p = null)));
        };
      }

      let firstFn = firstPromise(promiseFn);

      // 测试
      // firstFn().then(console.log); // 1
      // firstFn().then(console.log); // 1
      // firstFn().then(console.log); // 1
      // setTimeout(() => {
      //   firstFn().then(console.log); // 2
      //   firstFn().then(console.log); // 2
      //   firstFn().then(console.log); // 2
      // }, 3000);

      // 可以看到虽然我们调用了firstFn6次，但是实际请求只发生了两次（因为count只由1变成了2）
    }

    {
      // 请求文本过大，页面白屏处理？
      async function loadText() {
        const url =
          'https://duyi-static.oss-cn-beijing.aliyuncs.com/files/nove1.txt';
        const resp = await fetch(url);
        // const text = await resp.text();
        // console.log('text: ', text);
        // =====我是分割线=====
        // 如果网络慢，加载文本文本过大，会造成页面白屏
        // =====我是分割线=====
        const reader = resp.body.getReader();
        const decoder = new TextDecoder(); // 人本解码器

        for (; ;) {
          const { value, done } = await reader.read();
          if (done) {
            break;
          }

          const text = decoder.decode(value);
          console.log('text: ', text);
        }
      }
      // loadText();
    }
  </script>
</body>

</html>